/*
Copyright (C) BABEC. All rights reserved.
Copyright (C) THL A29 Limited, a Tencent company. All rights reserved.

SPDX-License-Identifier: Apache-2.0
*/

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"time"

	"chainmaker.org/chainmaker/contract-sdk-go/v2/pb/protogo"
	"chainmaker.org/chainmaker/contract-sdk-go/v2/sandbox"
	"chainmaker.org/chainmaker/contract-sdk-go/v2/sdk"
	"chainmaker.org/chainmaker/contract-utils/str"
)

const (
	keyStoreId = "i"
)

// StoreContract contract for store
type StoreContract struct {
}
type HistoryValue struct {
	Field       string      `json:"field"`
	Value       interface{} `json:"value"`
	TxId        string      `json:"txId"`
	Timestamp   string      `json:"timestamp"`
	BlockHeight int         `json:"blockHeight"`
	Key         string      `json:"key"`
}

// Store object to store
type Store struct {
	ProcessNum  string `json:"processNum"`
	ProcessInfo string `json:"processInfo"`
}

// NewStore create a store object
func NewStore(processNum string, processInfo string) *Store {
	store := &Store{
		ProcessNum:  processNum,
		ProcessInfo: processInfo,
	}
	return store
}

// InitContract 初始化合约
func (f *StoreContract) InitContract() protogo.Response {
	return sdk.Success([]byte("Init contract success"))
}

// UpgradeContract 升级合约
func (f *StoreContract) UpgradeContract() protogo.Response {
	return sdk.Success([]byte("Upgrade contract success"))
}

// InvokeContract 合约调用函数
func (f *StoreContract) InvokeContract(method string) protogo.Response {
	switch method {
	case "saveProcess":
		return f.saveProcess()
	case "findProcessByNum":
		return f.findContractByNum()
	case "findHisByNum":
		return f.findHisByNum()
	default:
		return sdk.Error("invalid method")
	}
}

// saveProcess 合约存证
func (f *StoreContract) saveProcess() protogo.Response {
	params := sdk.Instance.GetArgs()

	// 获取输入参数
	processNum := string(params["processNum"])
	processInfo := string(params["processInfo"])
	// 构建结构体
	store := NewStore(processNum, processInfo)

	// 序列化
	storeBytes, err := json.Marshal(store)
	if err != nil {
		return sdk.Error(fmt.Sprintf("marshal store failed, err: %s", err))
	}
	// 发送事件
	sdk.Instance.EmitEvent("processNum", []string{store.ProcessNum})

	// 存储数据
	err = sdk.Instance.PutStateByte(keyStoreId, store.ProcessNum, storeBytes)
	if err != nil {
		return sdk.Error("fail to save store bytes:" + err.Error())
	}

	// 记录日志
	sdk.Instance.Infof("[saveContract] processNum=" + store.ProcessNum)
	return sdk.Success([]byte(store.ProcessNum + "：上链成功"))
}

// findContractByNum 根据流程编号查找合同信息。
// 该方法首先从SDK实例中获取参数，然后根据流程编号查询存储状态，
// 如果查询成功则尝试反序列化结果并返回，否则返回错误信息。
func (f *StoreContract) findContractByNum() protogo.Response {
	// 获取参数
	params := sdk.Instance.GetArgs()
	processNum := string(params["processNum"])
	// 查询结果
	result, err := sdk.Instance.GetStateByte(keyStoreId, processNum)
	if err != nil {
		return sdk.Error("failed to call get_state")
	}
	sdk.Instance.Infof("[findContractByProcessNum] result=" + string(result))
	if len(result) == 0 {
		return sdk.Error("The processNum does not exist. Please check the process number.")
	}

	// 反序列化
	var store Store
	if err = json.Unmarshal(result, &store); err != nil {
		return sdk.Error(fmt.Sprintf("unmarshal store failed, err: %s", err))
	}

	// 记录日志
	sdk.Instance.Infof("[findContractByProcessNum] processNum=" + store.ProcessNum)

	// 返回结果
	return sdk.Success(result)
}

func (f *StoreContract) findHisByNum() protogo.Response {
	params := sdk.Instance.GetArgs()

	num := string(params["processNum"])
	if str.IsAnyBlank(num) {
		return sdk.Error("[findHisByNum]:num is empty")
	}
	stores, err := f.FindHisById(num)
	if err != nil {
		return sdk.Error("[FindHisById]" + err.Error())
	}
	return sdk.Success(stores)
}

// FindHisById 根据给定的ID查找历史记录。
// 该方法通过SDK实例创建一个历史键值迭代器，用于获取特定ID的所有历史记录。
// 参数:
//
//	id - 要查找历史记录的ID。
//
// 返回值:
//
//	[]byte - 包含历史记录的JSON字节流。
//	error - 如果查找过程中发生错误，则返回错误。
func (f *StoreContract) FindHisById(id string) ([]byte, error) {
	// 创建一个针对特定ID的历史键值迭代器。
	iter, err := sdk.Instance.NewHistoryKvIterForKey(keyStoreId, id)
	if err != nil {
		errMsg := fmt.Sprintf("new HistoryKvIter for key=[%s] failed, %s", id, err)
		return nil, errors.New(errMsg)
	}
	// 初始化一个Store结构体和一个HistoryValue切片来存储历史记录。
	var store Store
	tempHV := make([]HistoryValue, 0)

	// 遍历迭代器获取每个历史记录。
	for iter.HasNext() {
		km, err := iter.Next()
		if err != nil {
			errMsg := "iterator failed to get the next element" + "," + err.Error()
			sdk.Instance.Errorf(errMsg)
			// 避免出现EOF，暂时跳过
			continue
		}

		// 解析记录的值到Store结构体。
		err = json.Unmarshal(km.Value, &store)
		if err != nil {
			errMsg := "json parse element error" + "," + err.Error()
			sdk.Instance.Errorf(errMsg)
			continue
		}

		// 将时间戳转换为int64类型，并格式化为便于阅读的时间字符串。
		time64, _ := strconv.ParseInt(km.Timestamp, 10, 64)
		hv := &HistoryValue{
			TxId:        km.TxId,
			Timestamp:   Time2Str(time.Unix(time64, 0), "2006-01-02 15:04:05.000"),
			BlockHeight: km.BlockHeight,
			Key:         km.Key,
			Field:       km.Field,
			Value:       store,
		}

		// 将解析的历史记录添加到切片中。
		tempHV = append(tempHV, *hv)
	}

	// 关闭迭代器并检查是否关闭成功。
	closed, err := iter.Close()
	if !closed || err != nil {
		errMsg := fmt.Sprintf("iterator close failed, %s", err.Error())
		sdk.Instance.Errorf(errMsg)
		return nil, errors.New(errMsg)
	}

	// 将历史记录切片转换为JSON格式的字节流。
	data, err := json.Marshal(tempHV)
	if err != nil {
		return nil, err
	}

	// 返回JSON格式的历史记录字节流。
	return data, nil
}

// Time2Str 将时间转换为字符串。
// 参数 aTime: 需要转换的时间。
// 参数 pattern: 时间格式字符串，用于指定输出的时间格式。
// 返回值: 按照指定格式转换后的时间字符串。
// 该函数将给定的时间转换为上海时区的时间，并根据提供的格式模式将其格式化为字符串。
func Time2Str(aTime time.Time, pattern string) string {
	// 加载上海时区。
	loc, _ := time.LoadLocation("Asia/Shanghai")
	// 将给定时间转换到上海时区，并按照提供的格式模式格式化时间。
	return aTime.In(loc).Format(pattern)
}

func main() {
	err := sandbox.Start(new(StoreContract))
	if err != nil {
		sdk.Instance.Errorf(err.Error())
	}
}
